package com.dk.shared.proxy;

import com.dk.common.exception.BusinessException;
import com.dk.common.proxy.ProxyApi;
import com.dk.common.utils.SpringUtil;
import com.dk.shared.autoconfigure.common.SharedProperties;
import com.dk.shared.utils.UserAuditorHelper;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.IIOException;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TimeZone;

/**
 * @Description
 * @Author 王大凯
 * @Date 2024-01-18 10:37
 */
@Slf4j
public class ProxyApiInvocationHandler implements InvocationHandler {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    private RestTemplate restTemplate;

    public static final ObjectMapper objectMapper = new ObjectMapper();

    static {
        //取消时间的转化格式，默认是时间戳,同时需要设置要表现的时间格式
        objectMapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false);
        //方法间调用都是走的接口，正常返回和调用接收是一个类，属性都能对应
        //.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER));
        objectMapper.registerModule(javaTimeModule);
        objectMapper.registerModule(new ParameterNamesModule(JsonCreator.Mode.DEFAULT));
        // 设置时区
        objectMapper.setTimeZone(TimeZone.getDefault());
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Object 方法，走原生方法,比如hashCode()
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(proxy, args);
        }
        // 其它走本地代理
        return invoke1(proxy, method, args);
    }




    public Object invoke1(Object targetClass, Method method, Object[] args) throws Throwable {
        if (restTemplate == null) {
            restTemplate = SpringUtil.getBean(RestTemplate.class);
        }
        Class<?> clazz = targetClass.getClass().getInterfaces()[0];
        ProxyApi proxyApi = AnnotationUtils.getAnnotation(clazz, ProxyApi.class);
        if (proxyApi == null) {
            throw new BusinessException("clusterServiceApi null!");
        }
        //通过实例化的对象，调用无参数的方法
        String[] paths;
        RequestMapping requestMapping = AnnotationUtils.getAnnotation(method, RequestMapping.class);
        if (requestMapping == null) {
            throw new BusinessException("requestMapping null!");
        }

        Object jsonParam = null;
        Map<String, Object> formData = new HashMap<>();
        boolean requestBody = false;
        boolean upload = false;

        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        Parameter[] parameters = method.getParameters();
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                Annotation[] annotations = parameterAnnotations[i];
                if (annotations == null || annotations.length == 0) {
                    throw new BusinessException("param has none Annotation!");
                }
                boolean requestAnnotation = false;
                for (Annotation annotation : annotations) {
                    if (annotation instanceof RequestBody) {
                        if (requestBody) {
                            throw new BusinessException("RequestBody param more than one!");
                        }
                        jsonParam = args[i];
                        requestBody = true;
                        requestAnnotation = true;
                        continue;
                    }
                    if (annotation instanceof RequestParam || annotation instanceof PathVariable) {
                        if (requestAnnotation) {
                            throw new BusinessException(" RequestAnnotation of param more than one!");
                        }
                        formData.put(parameters[i].getName(), args[i]);
                        requestAnnotation = true;
                        //判断是否文件
                        if (args[i] instanceof MultipartFile || args[i] instanceof MultipartFile[]) {
                            if (annotation instanceof PathVariable) {
                                throw new BusinessException("file param can not be PathVariable!");
                            }
                            upload = true;
                        }
                        continue;
                    }
                    throw new BusinessException("param has none RequestAnnotation!");
                }
            }
        }
        if (requestMapping.method().length != 1) {
            throw new BusinessException("requestMapping has not one method!");
        }
        RequestMethod requestMethod = requestMapping.method()[0];
        switch (requestMethod) {
            case GET -> paths = AnnotationUtils.getAnnotation(method, GetMapping.class).path();
            case POST -> paths = AnnotationUtils.getAnnotation(method, PostMapping.class).path();
            default -> throw new BusinessException("methodMapping not support!");
        }
        String url = proxyApi.server() + proxyApi.path();
        for (String path : paths) {
            url += path;
        }
        if (requestMethod == RequestMethod.GET) {
            if (!CollectionUtils.isEmpty(formData)) {
                StringBuilder queryBuilder = new StringBuilder();
                for (Map.Entry<String, Object> entry : formData.entrySet()) {
                    if (queryBuilder.length() > 0) {
                        queryBuilder.append("&");
                    }
                    queryBuilder.append(entry.getKey()).append("=").append(entry.getValue());
                }
                return url + "?" + queryBuilder;
            }
        }
        return restTemplate.execute(url, requestMethod.asHttpMethod()
                , createRequestCallback(requestMethod, requestBody, requestBody ? jsonParam : formData)
                , response ->
                    extractData(method,requestMethod,response)
                );


    }

    private static Object extractData(Method method,RequestMethod requestMethod,ClientHttpResponse response) throws IOException {
        HttpHeaders headers = response.getHeaders();
        if (MediaType.APPLICATION_JSON.equals(headers.getContentType())) {
            String str = IOUtils.toString(response.getBody(), StandardCharsets.UTF_8);
            if (method.getReturnType() == Void.class || method.getReturnType() == void.class) {
                if (!StringUtils.hasLength(str)) {
                    return null;
                } else {
                    checkApiResult(str);
                }
            }
            try {
                return objectMapper.readValue(str, new TypeReference<>() {
                    @Override
                    public Type getType() {
                        return method.getGenericReturnType();
                    }
                });
            } catch (Exception e) {
                log.error("parse result error {} " + e.getMessage());
                //尝试ApiResult结构去解析
                checkApiResult(str);
                throw new BusinessException(e.getMessage());
            }

        } else if (headers.getContentType() == MediaType.APPLICATION_OCTET_STREAM) {
            //下载文件 TODO ()
            return response.getBody();
        } else {
            throw new BusinessException("response type not support!");
        }
    }



    private static <T> RequestCallback createRequestCallback(RequestMethod method, boolean json, final T data) {
        return request -> {
            HttpHeaders headers = request.getHeaders();
            headers.add(SharedProperties.getTokenName(), UserAuditorHelper.retrieveCurrentToken().orElse(null));
            switch (method){
                case GET -> {
                    return;
                }
                case POST -> {
                    if (json) {
                        headers.setContentType(MediaType.APPLICATION_JSON);
                        request.getBody().write(objectMapper.writeValueAsBytes(data));
                    } else {
                        Map<String, Object> formData = (Map<String, Object>) data;

                        String boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"; // 假设的boundary
                        headers.set("Content-Type", MediaType.MULTIPART_FORM_DATA_VALUE + "; boundary=" + boundary);

                        for (Map.Entry<String, Object> entry : formData.entrySet()) {
                            if (entry.getValue() instanceof MultipartFile) {
                                MultipartFile file = (MultipartFile) entry.getValue();
                                String filePart = "--" + boundary + "\r\n" +
                                        "Content-Disposition: form-data; name=\"" + entry.getKey() + "\"; filename=\"" + file.getOriginalFilename() + "\"\r\n" +
                                        "Content-Type: " + file.getContentType() + "\r\n\r\n";
                                request.getBody().write(filePart.getBytes(StandardCharsets.UTF_8));
                                request.getBody().write(file.getBytes());
                                request.getBody().write("\r\n".getBytes(StandardCharsets.UTF_8));
                            } else {
                                String paramPart = "--" + boundary + "\r\n" +
                                        "Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n" +
                                        entry.getValue().toString() + "\r\n";
                                request.getBody().write(paramPart.getBytes(StandardCharsets.UTF_8));
                            }
                        }

                        // 结束multipart
                        String endPart = "--" + boundary + "--\r\n";
                        request.getBody().write(endPart.getBytes(StandardCharsets.UTF_8));
                    }
                }
                default -> throw new BusinessException("methodMapping not support!");
            }

        };
    }

    private static void checkApiResult(String str) {
        log.error("apiResult:{}", str);
        Map<String, Object> map;
        try {
            map = objectMapper.readValue(str, Map.class);
        } catch (Exception e) {
            throw new BusinessException("未知错误，联系管理员");
        }
        if (map.containsKey("code") && !Integer.valueOf(200).equals(map.get("code"))) {
            throw new BusinessException(map.get("msg").toString());
        } else {
            throw new BusinessException("未知错误，联系管理员");
        }
    }
}


